package com.wzp.puppet;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Point;
import android.hardware.input.InputManager;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.os.Build;
import android.os.SystemClock;
import android.support.v4.view.InputDeviceCompat;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import static android.system.OsConstants.EXIT_FAILURE;
import static java.lang.System.exit;


/**
 * 手机端主程序,执行该程序方法
 *  1.Main编译成dex可执行文件放到手机上，通过命令执行：启动Server
    2.做成app，点按钮直接调用Main里面方法main方法：启动Server
 */
public class Main {

    /**
     * 可对手机发送keyCode指令执行硬件操作
     */
    private  InputManager im;
    private  Method injectInputEventMethod;
    private  long downTime;

    private  float scale = 1;
    public LogListener logListener;
    LocalServerSocket serverSocket;
    LocalSocket socket;

    Context context;

    public Main(Context context) {
        this.context = context;
    }

    public void setLogListener(LogListener logListener) {
        this.logListener = logListener;
    }

    public void start() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {

        logListener.printLog("start!");

        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                logListener.printLog(e.getMessage());
            }
        });

        //创建一个名为“puppet-ver1”的服务端Socket
        serverSocket = new LocalServerSocket("puppet-ver1");

        init();

        //死循环一直监听另一端来的数据和发送数据
        while (true) {
            logListener.printLog("listen.....");
            try {
                //阻塞当前线程，直到接收到数据，如果没有收到数据，则一直在等待状态
                socket = serverSocket.accept();
                logListener.printLog("accepted...");
                read(socket);
                write(socket);
            } catch (Exception e) {
                logListener.printLog(e.getMessage());
                stop();
            }
        }
    }

    /**
     * 停止服务
     */
    public void stop() {
        exit(EXIT_FAILURE);
        if (serverSocket != null && socket != null) {
            try {
                socket.shutdownInput();
                socket.shutdownOutput();
                socket.close();
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
                logListener.printLog(e.getMessage());
            }
            serverSocket = null;
        }
    }

    /**
     * 初始化,获取Android系统中InputManager
     * InputManager see:https://www.jianshu.com/p/f05d6b05ba17
     */
    private  void init() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        /**
         * InputManager 是android系统的类，可以操作android手机
         */
        im = (InputManager) InputManager.class.getDeclaredMethod("getInstance", new Class[0]).invoke(null, new Object[0]);
        MotionEvent.class.getDeclaredMethod("obtain", new Class[0]).setAccessible(true);
        injectInputEventMethod = InputManager.class.getMethod("injectInputEvent", new Class[]{InputEvent.class, Integer.TYPE});
    }

    /**
     * 往pc端写数据：不断截屏往连接端发送数据
     */
    private  void write(final LocalSocket socket) {
        new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    final int VERSION = 2;
                    BufferedOutputStream outputStream = new BufferedOutputStream(socket.getOutputStream());
                    //子线程死循环，
                    while (true) {
                        //截图获取图片bitmap对象
                        Bitmap bitmap = screenshot();
                        //将图片压缩质量按60%比例压缩放入byteArrayOutputStream流中
                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                        bitmap.compress(Bitmap.CompressFormat.JPEG, 60, byteArrayOutputStream);

                        //用outputStream发送数据
                        outputStream.write(2);
                        writeInt(outputStream, byteArrayOutputStream.size());
                        outputStream.write(byteArrayOutputStream.toByteArray());
                        outputStream.flush();
                    }
                } catch (Throwable e) {
                    logListener.printLog(e.getMessage());
                }
            }
        }.start();
    }

    /**
     * 读取另一端的数据:判断指令
     */
    private  void read(final LocalSocket socket) {

        new Thread() {
            private final String DOWN = "DOWN";
            private final String MOVE = "MOVE";
            private final String UP = "UP";

            private final String MENU = "MENU";
            private final String HOME = "HOME";
            private final String BACK = "BACK";

            private final String DEGREE = "DEGREE";

            @Override
            public void run() {
                super.run();
                try {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    while (true) {
                        String line;
                        try {
                            //读取另一端发来是数据
                            line = reader.readLine();
                            if (line == null) {
                                return;
                            }
                        } catch (Exception e) {
                            logListener.printLog(e.getMessage());
                            return;
                        }
                        try {
                            //指令判断
                            if (line.startsWith(DOWN)) {
                                //DOWN 指令，
                                handlerDown(line.substring(DOWN.length()));
                            } else if (line.startsWith(MOVE)) {
                                handlerMove(line.substring(MOVE.length()));
                            } else if (line.startsWith(UP)) {
                                handlerUp(line.substring(UP.length()));
                            } else if (line.startsWith(MENU)) {
                                menu();
                            } else if (line.startsWith(HOME)) {
                                pressHome();
                            } else if (line.startsWith(BACK)) {
                                back();
                            } else if (line.startsWith(DEGREE)) {
                                //滑块缩放指令，改变scale缩放值，下次截图将会按scale比例截图
                                scale = Float.parseFloat(line.substring(DEGREE.length())) / 100;
                            }
                        } catch (Exception e) {
                            logListener.printLog(e.getMessage());
                        }


                    }
                } catch (Throwable e) {
                    logListener.printLog(e.getMessage());
                }
            }
        }.start();
    }

    private  void handlerUp(String line) {
        Point point = getXY(line);
        if (point != null) {
            try {
                touchUp(point.x, point.y);
            } catch (Exception e) {
                logListener.printLog(e.getMessage());
            }
        }
    }

    private  void handlerMove(String line) {
        Point point = getXY(line);
        if (point != null) {
            try {
                touchMove(point.x, point.y);
            } catch (Exception e) {
                logListener.printLog(e.getMessage());
            }
        }
    }

    private  void handlerDown(String line) {
        Point point = getXY(line);
        if (point != null) {
            try {
                touchDown(point.x, point.y);
            } catch (Exception e) {
                logListener.printLog(e.getMessage());
            }
        }
    }


    private  Point getXY(String nums) {
        try {
            Point point = SurfaceControlVirtualDisplayFactory.getCurrentDisplaySize(false);
            String[] s = nums.split("#");
            float scaleX = Float.parseFloat(s[0]);
            float scaleY = Float.parseFloat(s[1]);
            point.x *= scaleX;
            point.y *= scaleY;
            return point;
        } catch (Exception e) {
            logListener.printLog(e.getMessage());
        }
        return null;
    }


    private  void writeInt(OutputStream outputStream, int v) throws IOException {
        outputStream.write(v >> 24);
        outputStream.write(v >> 16);
        outputStream.write(v >> 8);
        outputStream.write(v);
    }


    /**
     * 反射利用SurfaceControl类调用screenshot()方法截屏获取一张Bitmap图片
     */
    public  Bitmap screenshot() throws Exception {

        String surfaceClassName;
        Point size = SurfaceControlVirtualDisplayFactory.getCurrentDisplaySize(false);
        size.x *= scale;
        size.y *= scale;
        Bitmap bitmap = null;
        if (Build.VERSION.SDK_INT <= 17) {
            surfaceClassName = "android.view.Surface";
        } else {
            surfaceClassName = "android.view.SurfaceControl";
           
        }

        bitmap = (Bitmap) Class.forName(surfaceClassName).getDeclaredMethod("screenshot", new Class[]{Integer.TYPE, Integer.TYPE}).invoke(null, new Object[]{Integer.valueOf(size.x), Integer.valueOf(size.y)});

        Matrix m = new Matrix();

        return Bitmap.createBitmap(bitmap, 0, 0, size.x, size.y, m, false);

    }

    private  void menu() throws InvocationTargetException, IllegalAccessException {
        sendKeyEvent(im, injectInputEventMethod, InputDeviceCompat.SOURCE_KEYBOARD, KeyEvent.KEYCODE_MENU, false);
    }

    private  void back() throws InvocationTargetException, IllegalAccessException {
        sendKeyEvent(im, injectInputEventMethod, InputDeviceCompat.SOURCE_KEYBOARD, 4, false);
    }


    private  void touchUp(float clientX, float clientY) throws InvocationTargetException, IllegalAccessException {
        injectMotionEvent(im, injectInputEventMethod, InputDeviceCompat.SOURCE_TOUCHSCREEN, 1, downTime, SystemClock.uptimeMillis(), clientX, clientY, 1.0f);
    }

    private  void touchMove(float clientX, float clientY) throws InvocationTargetException, IllegalAccessException {
        injectMotionEvent(im, injectInputEventMethod, InputDeviceCompat.SOURCE_TOUCHSCREEN, 2, downTime, SystemClock.uptimeMillis(), clientX, clientY, 1.0f);
    }

    private  void touchDown(float clientX, float clientY) throws InvocationTargetException, IllegalAccessException {
        downTime = SystemClock.uptimeMillis();
        injectMotionEvent(im, injectInputEventMethod, InputDeviceCompat.SOURCE_TOUCHSCREEN, 0, downTime, downTime, clientX, clientY, 1.0f);

    }


    private  void pressHome() throws InvocationTargetException, IllegalAccessException {
        sendKeyEvent(im, injectInputEventMethod, InputDeviceCompat.SOURCE_KEYBOARD, 3, false);
    }


    private  void injectMotionEvent(InputManager im, Method injectInputEventMethod, int inputSource, int action, long downTime, long eventTime, float x, float y, float pressure) throws InvocationTargetException, IllegalAccessException {
        MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, x, y, pressure, 1.0f, 0, 1.0f, 1.0f, 0, 0);
        event.setSource(inputSource);
        injectInputEventMethod.invoke(im, new Object[]{event, Integer.valueOf(0)});
    }

    private  void injectKeyEvent(InputManager im, Method injectInputEventMethod, KeyEvent event) throws InvocationTargetException, IllegalAccessException {
        injectInputEventMethod.invoke(im, new Object[]{event, Integer.valueOf(0)});
    }


    private  void sendKeyEvent(InputManager im, Method injectInputEventMethod, int inputSource, int keyCode, boolean shift) throws InvocationTargetException, IllegalAccessException {
        long now = SystemClock.uptimeMillis();
        int meta = shift ? 1 : 0;
        injectKeyEvent(im, injectInputEventMethod, new KeyEvent(now, now, 0, keyCode, 0, meta, -1, 0, 0, inputSource));
        injectKeyEvent(im, injectInputEventMethod, new KeyEvent(now, now, 1, keyCode, 0, meta, -1, 0, 0, inputSource));
    }

    
    interface LogListener {
        void printLog(String log);
    }
}
